package com.rtsapp.server.gamelog.analyze.stat.income;

import com.rtsapp.server.gamelog.analyze.dao.IncomeDao;
import com.rtsapp.server.gamelog.analyze.stat.DateUtils;
import com.rtsapp.server.gamelog.analyze.stat.DoubleUtils;
import com.rtsapp.server.gamelog.analyze.stat.ExchangeRateMgr;
import com.rtsapp.server.gamelog.analyze.stat.MailUtils;
import com.rtsapp.server.gamelog.analyze.stat.income.model.GameIdAndAreaIdInfo;
import com.rtsapp.server.gamelog.analyze.stat.income.model.IncomeCacheData;
import com.rtsapp.server.gamelog.analyze.stat.income.model.IncomeQueryParam;
import com.rtsapp.server.gamelog.analyze.stat.income.model.UserCacheData;

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentMap;

/**
 * Created by liwang on 17-1-11.
 * <p>
 * 管理收入和用户相关的数据库数据
 */
public class IncomeDataMgr {

    private final IncomeDao incomeDao;
    private final Timer timer;

    // 存储所有适合保存的收入数据 (如: 昨日全天收入)
    private volatile Map<String, IncomeCacheData> incomeMap = new HashMap<>( );
    // 存储所有适合保存的用户数据
    private volatile Map<String, UserCacheData> userMap = new HashMap<>( );
    // 存储的数剧查询的时间点, 用于判断使用数据时数据是否过期
    private volatile long queryTime;

    private List<IncomeViewData> incomeViewDataList = null;
    private List<UserViewData> userViewDataList = null;


    // 收入指标管理器
    private final IncomeTargetMgr incomeTargetMgr;
    // 游戏信息管理器
    private final IncomeGameInfoMgr incomeGameInfoMgr;


    public IncomeDataMgr( IncomeDao incomeDao ) {
        this.incomeDao = incomeDao;
        this.timer = new Timer( );

        // 创建收入指标管理器
        this.incomeTargetMgr = new IncomeTargetMgr( incomeDao );
        // 创建游戏信息管理器
        this.incomeGameInfoMgr = new IncomeGameInfoMgr( incomeDao, timer );


        // 3分钟查询一次
        timer.scheduleAtFixedRate( new TimerTask( ) {

            private final SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );

            @Override
            public void run( ) {
                long time1 = System.currentTimeMillis( );
                incomeViewDataList = getIncomeInfo( time1 );
                userViewDataList = getUserInfo( time1 );

                // 输出查询用时
                long time2 = System.currentTimeMillis( );
                long timeUsed = ( time2 - time1 ) / 1000;
                System.out.println( "\n" + sdf.format( time2 ) + ": 本次查询用时 " + timeUsed + " 秒" );

                // 如果查询时间大于60秒, 发送警告邮件
                if ( timeUsed >= 60 ) {
                    MailUtils.getInstance( ).sendMail( "收入信息查询时间过长", "收入信息查询时间过长! \n查询用时 " + timeUsed + "秒" );
                }

            }
        }, 0, 180000 );
    }


    private String getKey( String gameId, String areaId ) {
        return gameId + areaId;
    }

    private IncomeCacheData getIncomeCacheData( final long time_MS, String key ) {
        IncomeCacheData incomeCacheData = incomeMap.get( key );
        if ( incomeCacheData == null ) {
            reloadData( time_MS );
            incomeCacheData = incomeMap.get( key );
        }

        return incomeCacheData;
    }

    private UserCacheData getUserCacheData( final long time_MS, String key ) {
        UserCacheData userCacheData = userMap.get( key );
        if ( userCacheData == null ) {
            reloadData( time_MS );
            userCacheData = userMap.get( key );
        }

        return userCacheData;
    }

    public void reloadData( final long time_MS ) {
        Map<String, IncomeCacheData> incomeMap = new HashMap<>( );
        Map<String, UserCacheData> userMap = new HashMap<>( );
        List<GameIdAndAreaIdInfo> list = incomeDao.selectAllGameIdAndAreaIdFromGameInfo( );
        for ( GameIdAndAreaIdInfo gameIdAndAreaId : list ) {
            final String gameId = gameIdAndAreaId.gameId;
            final String areaId = gameIdAndAreaId.areaId;
            final String key = getKey( gameId, areaId );

            IncomeCacheData incomeCacheData = incomeMap.get( key );
            if ( incomeCacheData == null ) {
                incomeCacheData = new IncomeCacheData( );
                incomeMap.put( key, incomeCacheData );
            }

            UserCacheData userCacheData = userMap.get( key );
            if ( userCacheData == null ) {
                userCacheData = new UserCacheData( );
                userMap.put( key, userCacheData );
            }

            Calendar cal = Calendar.getInstance( );
            cal.setTimeInMillis( time_MS );
            cal.set( Calendar.HOUR_OF_DAY, 0 );
            cal.set( Calendar.MINUTE, 0 );
            cal.set( Calendar.SECOND, 0 );
            cal.set( Calendar.MILLISECOND, 0 );

            Timestamp todayStart = new Timestamp( cal.getTimeInMillis( ) );
            Timestamp yesterdayStart = new Timestamp( cal.getTimeInMillis( ) - DateUtils.DAY_IN_MILLIS );
            Timestamp dayBeforeYesterdayStart = new Timestamp( cal.getTimeInMillis( ) - DateUtils.DAY_IN_MILLIS * 2 );

            IncomeQueryParam yesterdayParam = new IncomeQueryParam( gameId, areaId, yesterdayStart, todayStart );
            // income 昨日总计充值
            incomeCacheData.setYesterdayTotal( incomeDao.selectIncome( yesterdayParam ) );
            // user 昨日DAU
            userCacheData.setYesterdayDAU( incomeDao.selectDAU( yesterdayParam ) );
            // user 昨日APA
            userCacheData.setYesterdayAPA( incomeDao.selectAPA( yesterdayParam ) );

            IncomeQueryParam dayBeforeYesterdayParam = new IncomeQueryParam( gameId, areaId, dayBeforeYesterdayStart, yesterdayStart );
            // user 昨日次留 (前日新用户 昨日登录)
            List<Integer> createUserList = incomeDao.selectUserCreate( dayBeforeYesterdayParam );
            List<Integer> loginUserList = incomeDao.selectUserLogin( yesterdayParam );
            float retention = 0;
            for ( int userId : createUserList ) {
                if ( loginUserList.contains( userId ) ) {
                    retention++;
                }
            }

            userCacheData.setYesterdayRetention1( retention / createUserList.size( ) );

            // income 前日总计充值
            incomeCacheData.setDayBeforeYesterdayTotal( incomeDao.selectIncome( dayBeforeYesterdayParam ) );


            // income 本月收入累加 (不包含今日实时)
            // user 本月新增用户累加 (不包含今日)
            if ( cal.get( Calendar.DAY_OF_MONTH ) == 1 ) {
                incomeCacheData.setMonthTotal( 0 );
                userCacheData.setMonthIncrement( 0 );
            } else {
                cal.set( Calendar.DAY_OF_MONTH, 1 );
                Timestamp monthStart = new Timestamp( cal.getTimeInMillis( ) );
                IncomeQueryParam param = new IncomeQueryParam( gameId, areaId, monthStart, todayStart );

                incomeCacheData.setMonthTotal( incomeDao.selectIncome( param ) );
                userCacheData.setMonthIncrement( incomeDao.selectUserIncrement( param ) );
            }

        }

        this.incomeMap = incomeMap;
        this.userMap = userMap;
        this.queryTime = time_MS;
    }

    private void checkData( final long time_MS ) {
        if ( DateUtils.isSameDay( time_MS, queryTime ) ) {
            return;
        }

        synchronized ( this ) {
            if ( DateUtils.isSameDay( time_MS, queryTime ) ) {
                return;
            }

            reloadData( time_MS );

        }

    }

    public List<IncomeViewData> getIncomeInfo( final long time_MS ) {
        checkData( time_MS );

        List<IncomeViewData> list = incomeDao.selectIncomeViewDataFromGameInfo( );
        if ( list == null || list.isEmpty( ) ) {
            return null;
        }


        Calendar cal = Calendar.getInstance( );
        cal.setTimeInMillis( time_MS );
        cal.set( Calendar.HOUR_OF_DAY, 0 );
        cal.set( Calendar.MINUTE, 0 );
        cal.set( Calendar.SECOND, 0 );
        cal.set( Calendar.MILLISECOND, 0 );

        Timestamp todayReal = new Timestamp( time_MS );
        Timestamp yesterdayReal = new Timestamp( time_MS - DateUtils.DAY_IN_MILLIS );
        Timestamp todayStart = new Timestamp( cal.getTimeInMillis( ) );
        Timestamp yesterdayStart = new Timestamp( cal.getTimeInMillis( ) - DateUtils.DAY_IN_MILLIS );


        // 总计数据
        IncomeViewData totalIncomeViewData = new IncomeViewData( );
        for ( int i = 0; i < list.size( ); i++ ) {
            IncomeViewData resultData = list.get( i );

            if ( i == 0 ) {
                totalIncomeViewData.setGameId( resultData.getGameId( ) );
                totalIncomeViewData.setGameName( resultData.getGameName( ) );
                totalIncomeViewData.setAreaName( "合计" );
            } else if ( !resultData.getGameId( ).equals( totalIncomeViewData.getGameId( ) ) ) {
                list.add( i++, totalIncomeViewData );
                list.add( i++, new IncomeViewData( ) );
                totalIncomeViewData = new IncomeViewData( );
                totalIncomeViewData.setGameId( resultData.getGameId( ) );
                totalIncomeViewData.setGameName( resultData.getGameName( ) );
                totalIncomeViewData.setAreaName( "合计" );

            }

            String gameId = resultData.getGameId( );
            String areaId = resultData.getAreaId( );
            IncomeCacheData incomeCacheData = getIncomeCacheData( time_MS, getKey( gameId, areaId ) );
            if ( incomeCacheData == null ) {
                continue;
            }

            // 汇率
            float exchangeRate = getExchangeRateFromDB( gameId, areaId, incomeDao );

            // 今日实时收入
            resultData.setTodayRealTime( ( int ) ( incomeDao.selectIncome( new IncomeQueryParam( gameId, areaId, todayStart, todayReal ) ) * exchangeRate ) );

            // 昨日实时收入
            resultData.setYesterdayRealTime( ( int ) ( incomeDao.selectIncome( new IncomeQueryParam( gameId, areaId, yesterdayStart, yesterdayReal ) ) * exchangeRate ) );

            cal.setTimeInMillis( time_MS );
            // 月指标
            int monthIncomeTarget = incomeTargetMgr.getIncomeTargetByMonth( gameId, areaId, cal.get( Calendar.YEAR ), cal.get( Calendar.MONTH ) );
            resultData.setMonthIncomeTarget( monthIncomeTarget );
            // 日均收入指标
            resultData.setDayAvgTarget( monthIncomeTarget / cal.getActualMaximum( Calendar.DAY_OF_MONTH ) );

            // 昨日总计收入
            resultData.setYesterdayTotal( ( int ) ( incomeCacheData.getYesterdayTotal( ) * exchangeRate ) );
            // 前日总计收入
            resultData.setDayBeforeYesterdayTotal( ( int ) ( incomeCacheData.getDayBeforeYesterdayTotal( ) * exchangeRate ) );
            // 月累计收入
            resultData.setMonthTotal( ( int ) ( incomeCacheData.getMonthTotal( ) * exchangeRate ) + resultData.getTodayRealTime( ) );


            // 计算总计数据
            totalIncomeViewData.setTodayRealTime( totalIncomeViewData.getTodayRealTime( ) + resultData.getTodayRealTime( ) );
            totalIncomeViewData.setYesterdayRealTime( totalIncomeViewData.getYesterdayRealTime( ) + resultData.getYesterdayRealTime( ) );
            totalIncomeViewData.setYesterdayTotal( totalIncomeViewData.getYesterdayTotal( ) + resultData.getYesterdayTotal( ) );
            totalIncomeViewData.setDayBeforeYesterdayTotal( totalIncomeViewData.getDayBeforeYesterdayTotal( ) + resultData.getDayBeforeYesterdayTotal( ) );
            totalIncomeViewData.setDayAvgTarget( totalIncomeViewData.getDayAvgTarget( ) + resultData.getDayAvgTarget( ) );
            totalIncomeViewData.setMonthIncomeTarget( totalIncomeViewData.getMonthIncomeTarget( ) + resultData.getMonthIncomeTarget( ) );
            totalIncomeViewData.setMonthTotal( totalIncomeViewData.getMonthTotal( ) + resultData.getMonthTotal( ) );


        }


        // 最后添加一条总计数据
        list.add( totalIncomeViewData );
        return list;
    }

    public List<UserViewData> getUserInfo( final long time_MS ) {
        checkData( time_MS );

        List<UserViewData> list = incomeDao.selectGameIdGameNameAreaIdAreaNameFromGameInfo( );
        if ( list == null || list.isEmpty( ) ) {
            return null;
        }

        Calendar cal = Calendar.getInstance( );
        cal.setTimeInMillis( time_MS );
        cal.set( Calendar.HOUR_OF_DAY, 0 );
        cal.set( Calendar.MINUTE, 0 );
        cal.set( Calendar.SECOND, 0 );
        cal.set( Calendar.MILLISECOND, 0 );

        Timestamp todayReal = new Timestamp( time_MS );
        Timestamp todayStart = new Timestamp( cal.getTimeInMillis( ) );


        // 总计数据
        UserViewData totalUserViewData = new UserViewData( );
        float totalYesterdayRetention = 0;
        int num = 0;
        for ( int i = 0; i < list.size( ); i++ ) {
            UserViewData resultData = list.get( i );

            if ( i == 0 ) {
                totalUserViewData.setGameId( resultData.getGameId( ) );
                totalUserViewData.setGameName( resultData.getGameName( ) );
                totalUserViewData.setAreaName( "合计" );
            } else if ( !resultData.getGameId( ).equals( totalUserViewData.getGameId( ) ) ) {
                totalUserViewData.setYesterdayRetention1( DoubleUtils.floatToPercentKeep2( totalYesterdayRetention / num ) );
                list.add( i++, totalUserViewData );
                list.add( i++, new UserViewData( ) );
                totalUserViewData = new UserViewData( );
                totalUserViewData.setGameId( resultData.getGameId( ) );
                totalUserViewData.setGameName( resultData.getGameName( ) );
                totalUserViewData.setAreaName( "合计" );

                totalYesterdayRetention = 0;
                num = 0;
            }

            String gameId = resultData.getGameId( );
            String areaId = resultData.getAreaId( );
            UserCacheData userCacheData = getUserCacheData( time_MS, getKey( gameId, areaId ) );

            IncomeQueryParam todayParam = new IncomeQueryParam( gameId, areaId, todayStart, todayReal );

            // 今日新增
            resultData.setTodayIncrement( incomeDao.selectUserIncrement( todayParam ) );
            // 今日DAU
            resultData.setTodayDAU( incomeDao.selectDAU( todayParam ) );
            // 今日APA
            resultData.setTodayAPA( incomeDao.selectAPA( todayParam ) );


            // 月新增总量
            resultData.setMonthIncrement( userCacheData.getMonthIncrement( ) + resultData.getTodayIncrement( ) );
            // 昨日DAU
            resultData.setYesterdayDAU( userCacheData.getYesterdayDAU( ) );
            // 昨日APA
            resultData.setYesterdayAPA( userCacheData.getYesterdayAPA( ) );
            // 昨日次留
            float yesterdayRetention = userCacheData.getYesterdayRetention1( );
            resultData.setYesterdayRetention1( DoubleUtils.floatToPercentKeep2( yesterdayRetention ) );

            // 计算合计数据
            totalUserViewData.setTodayIncrement( totalUserViewData.getTodayIncrement( ) + resultData.getTodayIncrement( ) );
            totalUserViewData.setMonthIncrement( totalUserViewData.getMonthIncrement( ) + resultData.getMonthIncrement( ) );
            totalUserViewData.setTodayDAU( totalUserViewData.getTodayDAU( ) + resultData.getTodayDAU( ) );
            totalUserViewData.setYesterdayDAU( totalUserViewData.getYesterdayDAU( ) + resultData.getYesterdayDAU( ) );
            totalUserViewData.setTodayAPA( totalUserViewData.getTodayAPA( ) + resultData.getTodayAPA( ) );
            totalUserViewData.setYesterdayAPA( totalUserViewData.getYesterdayAPA( ) + resultData.getYesterdayAPA( ) );
            totalYesterdayRetention += yesterdayRetention;
            num++;

        }

        totalUserViewData.setYesterdayRetention1( DoubleUtils.floatToPercentKeep2( totalYesterdayRetention / num ) );
        list.add( totalUserViewData );

        return list;
    }

    private float getMonthIncomeTarget( Calendar cal, String gameId, String areaId ) {

        String dateAndValueStr = incomeDao.selectMonthTarget( gameId, areaId );
        if ( dateAndValueStr == null || ( dateAndValueStr = dateAndValueStr.trim( ) ).isEmpty( ) ) {
            return 0;
        }

        String[] dateAndValue = dateAndValueStr.split( "#" );
        if ( dateAndValue.length != 2 ) {
            return 0;
        }

        String yearMonth = cal.get( Calendar.YEAR ) + String.valueOf( cal.get( Calendar.MONTH ) + 1 );

        if ( !yearMonth.equals( dateAndValue[ 0 ] ) ) {
            return 0;
        }

        return Float.parseFloat( String.format( "%.2f", Float.parseFloat( dateAndValue[ 1 ] ) ) );
    }


    private float getExchangeRate( String gameId, String areaId, IncomeDao incomeDao, long time_MS ) {
        Float exchangeRate = ExchangeRateMgr.getInstance( ).getRealTimeExchangeRate( incomeDao.getCurrencyByGameIdAndAreaId( gameId, areaId ), time_MS );
        return ( exchangeRate == null || exchangeRate <= 0 ) ? 1 : exchangeRate;
    }

    private float getExchangeRateFromDB( String gameId, String areaId, IncomeDao incomeDao ) {
        String exchangeRateStr = incomeDao.selectExchangeRate( gameId, areaId );
        try {
            return Float.parseFloat( exchangeRateStr );
        } catch ( Throwable e ) {
            return 1;
        }
    }


    public List<IncomeViewData> getIncomeViewDataList( ) {
        return incomeViewDataList;
    }

    public List<UserViewData> getUserViewDataList( ) {
        return userViewDataList;
    }

    public ConcurrentMap<Integer, Integer> getIncomeTargetByYear( String gameId, String areaId, int curYear ) {
        return incomeTargetMgr.getIncomeTargetByYear( gameId, areaId, curYear, curYear );
    }

    public void updateIncomeTarget( String gameId, String areaId, int year, int month, int incomeTarget ) {
        incomeTargetMgr.updateIncomeTarget( gameId, areaId, year, month, incomeTarget );
    }

}
